﻿//#define USE_DUNGEONBUDDY_DLL

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using CommonBehaviors.Actions;
using Styx;
using Styx.Common.Helpers;
using Styx.CommonBot;
using Styx.CommonBot.Frames;
using Styx.Helpers;
using Styx.Patchables;
using Styx.Pathing;
using Styx.TreeSharp;
using Styx.WoWInternals;
using Styx.WoWInternals.WoWObjects;
using Action = Styx.TreeSharp.Action;
#if USE_DUNGEONBUDDY_DLL
using Bots.DungeonBuddyDll;
using Bots.DungeonBuddyDll.Profiles;
using Bots.DungeonBuddyDll.Attributes;
using Bots.DungeonBuddyDll.Helpers;

namespace Bots.DungeonBuddyDll.Dungeon_Scripts.Mists_of_Pandaria
#else
    using Bots.DungeonBuddy.Profiles;
    using Bots.DungeonBuddy.Attributes;
    using Bots.DungeonBuddy.Helpers;
namespace Bots.DungeonBuddy.Dungeon_Scripts.Mists_of_Pandaria
#endif
{
    internal class MogushanPalace : Dungeon
    {
        #region Overrides of Dungeon

        public override uint DungeonId { get { return 467; } }

        public override WoWPoint Entrance { get { return new WoWPoint(1400.152, 428.4431, 479.0349); } }

        private readonly WaitTimer _ignoreGreenhornTimer = new WaitTimer(TimeSpan.FromSeconds(20));
        private readonly WaitTimer _killGreenhornTimer = new WaitTimer(TimeSpan.FromSeconds(10));
        public override void RemoveTargetsFilter(List<WoWObject> units)
        {
            units.RemoveAll(
                ret =>
                {
                    var unit = ret.ToUnit();
                    if (unit != null)
                    {
                        if (unit.Entry == GlintrokGreenhornId)
                        {
                            if (_ignoreGreenhornTimer.IsFinished)
                            {
                                _ignoreGreenhornTimer.Reset();
                                _killGreenhornTimer.Reset();
                            }
                            if (!_killGreenhornTimer.IsFinished && StyxWoW.Me.IsTank())
                            {
                                return true;
                            }
                        }
                        if (unit.Entry == MuShibaId)
                            return true;

                        if (unit.Entry == MingTheCunning && unit.CastingSpellId == MagneticFieldId && Me.IsMelee())
                            return true;
                        // don't pull these NPCs if they're in combat with another NPC.
                        if ((unit.Entry == KargeshRibcrusherId || unit.Entry == HarthakStormcallerId) && unit.Combat && Me.Combat && !unit.TaggedByMe)
                            return true;
                    }
                    return false;
                });
        }

        public override void IncludeTargetsFilter(List<WoWObject> incomingunits, HashSet<WoWObject> outgoingunits)
        {
            foreach (var obj in incomingunits)
            {
                var unit = obj as WoWUnit;
                if (unit != null)
                {
                }
            }
        }

        public override void WeighTargetsFilter(List<Targeting.TargetPriority> units)
        {
            foreach (var priority in units)
            {
                var unit = priority.Object as WoWUnit;
                if (unit != null)
                {
                    if (unit.Entry == GlintrokIronhideId)
                        priority.Score += 600;
                    if (unit.Entry == GlintrokHexxerId)
                        priority.Score += 550;
                    if (unit.Entry == GlintrokSkulkerId)
                        priority.Score += 500;
                    if (unit.Entry == GlintrokOracleId)
                        priority.Score += 450;
                }
            }
        }

        #region Elevator

        private const float ElevatorBottomZ = -40.35011f;
        private const float ElevatorTopZ = 21.66621f;
        private const uint ElevatorId = 212162;
        private readonly WoWPoint _elevatorBottomBoardLoc = new WoWPoint(-4399.657, -2751.014, -39.71338);
        private readonly WoWPoint _elevatorBottomWaitLoc = new WoWPoint(-4399.436, -2742.337, -40.023);
        private readonly WoWPoint _elevatorTopBoardLoc = new WoWPoint(-4399.657, -2751.014, 22.30293);
        private readonly WoWPoint _elevatorTopWaitLoc = new WoWPoint(-4399.475, -2741.645, 22.3338);

        public override MoveResult MoveTo(WoWPoint location)
        {
            return !ScriptHelpers.IsBossAlive("Gekkan") && ElevatorBehavior(location) ? MoveResult.Moved : base.MoveTo(location);
        }

        private Composite CreateFollowerElevatorBehavior()
        {
            return new Decorator(
                ctx => !StyxWoW.Me.IsTank(),
                new Action(
                    ctx =>
                    {
                        var tankI = StyxWoW.Me.GroupInfo.RaidMembers.FirstOrDefault(p => p.HasRole(WoWPartyMember.GroupRole.Tank));

                        if (tankI != null)
                        {
                            var tank = tankI.ToPlayer();
                            var myFloorLevel = FloorLevel(Me.Location);
                            var tankLoc = tank != null ? tank.Location : tankI.Location3D;
                            var tankLevel = FloorLevel(tankLoc);
                            var elevatorRestingZ = myFloorLevel == 1 ? ElevatorBottomZ : ElevatorTopZ;
                            var elevatorBoardLoc = myFloorLevel == 2 ? _elevatorTopBoardLoc : _elevatorBottomBoardLoc;
                            var elevatorWaitLoc = myFloorLevel == 2 ? _elevatorTopWaitLoc : _elevatorBottomWaitLoc;

                            // do we need to get on a lift?
                            if (IsOnLift(tank) && !IsOnLift(Me) && tankLevel == myFloorLevel)
                            {
                                var ele = ObjectManager.GetObjectsOfType<WoWGameObject>().FirstOrDefault(g => g.Entry == ElevatorId);
                                bool elevatorIsReadyToBoard = ele != null && Math.Abs(ele.Z - elevatorRestingZ) <= 0.5f;
                                if (elevatorIsReadyToBoard)
                                {
                                    if (Me.Location.DistanceSqr(elevatorBoardLoc) > 3 * 3)
                                    {
                                        Logger.Write("[Elevator Manager] Boarding Elevator");
                                        Navigator.PlayerMover.MoveTowards(elevatorBoardLoc);
                                    }
                                    else
                                    {
                                        Logger.Write("[Elevator Manager] Jumping");
                                        WoWMovement.Move(WoWMovement.MovementDirection.JumpAscend);
                                        WoWMovement.MoveStop(WoWMovement.MovementDirection.JumpAscend);
                                    }
                                }
                                return RunStatus.Success;
                            }
                            // do we need to get off lift?
                            if (!IsOnLift(tank) && IsOnLift(Me) && tankLevel == myFloorLevel)
                            {
                                var ele = ObjectManager.GetObjectsOfType<WoWGameObject>().FirstOrDefault(g => g.Entry == ElevatorId);
                                bool elevatorIsReadyToBoard = ele != null && Math.Abs(ele.Z - elevatorRestingZ) <= 0.5f;
                                if (elevatorIsReadyToBoard)
                                {
                                    Logger.Write("[Elevator Manager] Exiting Elevator");
                                    Navigator.PlayerMover.MoveTowards(elevatorWaitLoc);
                                }
                                return RunStatus.Success;
                            }
                        }
                        return RunStatus.Failure;
                    }));
        }

        public bool ElevatorBehavior(WoWPoint destination)
        {
            var myloc = StyxWoW.Me.Location;

            var myFloorLevel = FloorLevel(myloc);
            var destinationFloorLevel = FloorLevel(destination);

            if (myFloorLevel != destinationFloorLevel)
            {
                var ele = ObjectManager.GetObjectsOfType<WoWGameObject>().FirstOrDefault(g => g.Entry == ElevatorId);

                var elevatorRestingZ = myFloorLevel == 1 ? ElevatorBottomZ : ElevatorTopZ;
                var elevatorBoardLoc = destinationFloorLevel == 1 ? _elevatorTopBoardLoc : _elevatorBottomBoardLoc;
                var elevatorWaitLoc = destinationFloorLevel == 1 ? _elevatorTopWaitLoc : _elevatorBottomWaitLoc;
                bool elevatorIsReadyToBoard = ele != null && Math.Abs(ele.Z - elevatorRestingZ) <= 0.5f;
                // move to the lever loc
                if ((ele == null || myloc.DistanceSqr(elevatorBoardLoc) > 20 * 20 || !elevatorIsReadyToBoard && myloc.DistanceSqr(elevatorWaitLoc) > 4 * 4) && Me.TransportGuid == 0)
                {
                    Logger.Write("[Elevator Manager] Moving To Elevator");
                    var moveResult = Navigator.MoveTo(elevatorWaitLoc);
                    return moveResult != MoveResult.Failed && moveResult != MoveResult.PathGenerationFailed;
                }
                // get onboard of elevator.
                if (elevatorIsReadyToBoard && myloc.DistanceSqr(elevatorBoardLoc) > 3 * 3)
                {
                    Logger.Write("[Elevator Manager] Boarding Elevator");
                    // avoid getting stuck on lever
                    Navigator.PlayerMover.MoveTowards(elevatorBoardLoc);
                }
                else if (elevatorIsReadyToBoard && myloc.DistanceSqr(elevatorBoardLoc) <= 3 * 3 && Me.TransportGuid == 0)
                {
                    Logger.Write("[Elevator Manager] Jumping");
                    WoWMovement.Move(WoWMovement.MovementDirection.JumpAscend);
                    WoWMovement.MoveStop(WoWMovement.MovementDirection.JumpAscend);
                }
                return true;
            }

            // exit elevator
            var transport = Me.TransportGuid > 0 ? Me.Transport : null;
            if (transport != null && transport.Entry == ElevatorId)
            {
                Logger.Write("[Elevator Manager] Exiting Elevator");
                var elevatorExitZ = destinationFloorLevel == 1 ? ElevatorBottomZ : ElevatorTopZ;
                var elevatorExitLoc = destinationFloorLevel == 1 ? _elevatorBottomWaitLoc : _elevatorTopWaitLoc;
                bool elevatorIsReadyToExit = Math.Abs(transport.Z - elevatorExitZ) <= 0.5f;
                if (elevatorIsReadyToExit && myFloorLevel == destinationFloorLevel)
                    Navigator.PlayerMover.MoveTowards(elevatorExitLoc);
                return true;
            }
            return false;
        }

        private bool IsOnLift(WoWPlayer player)
        {
            return player != null && player.TransportGuid > 0 && player.Transport.Entry == ElevatorId;
        }

        private int FloorLevel(WoWPoint loc)
        {
            return loc.Z < -10 ? 1 : 2;
        }

        #endregion

        #endregion

        private const uint MuShibaId = 61453;
        private const uint MingTheCunning = 61444;
        private const uint HaiyanTheUnstoppable = 61445;
        private const uint KuaiTheBrute = 61442;
        private const int MagneticFieldId = 120100;
        const uint GlintrokOracleId = 61339;
        const uint GlintrokHexxerId = 61340;
        const uint GlintrokIronhideId = 61337;
        const uint GlintrokSkulkerId = 61338;
        const uint GlintrokGreenhornId = 61247;
        const uint HarthakStormcallerId = 61946;
        const uint KargeshRibcrusherId = 61947;

        private static LocalPlayer Me { get { return StyxWoW.Me; } }

        [EncounterHandler(64432, "Sinan the Dreamer", Mode = CallBehaviorMode.Proximity)]
        public Composite SinantheDreamerEncounter()
        {
            return new PrioritySelector(
                new Decorator<WoWUnit>(
                    u => u.QuestGiverStatus == QuestGiverStatus.Available,
                    ScriptHelpers.CreatePickupQuest(64432)),

                 new Decorator<WoWUnit>(
                    u => u.QuestGiverStatus == QuestGiverStatus.TurnIn,
                    ScriptHelpers.CreateTurninQuest(64432))
                    );
        }

        [EncounterHandler(61442, "Kuai the Brute", Mode = CallBehaviorMode.Proximity)]
        public Composite KuaitheBruteEncounter()
        {
            const int shockWave = 119922;
            WoWUnit boss = null;
            return new PrioritySelector(
                ctx => boss = ctx as WoWUnit,
                new Decorator(
                    ctx => boss.IsNeutral && ScriptHelpers.IsBossAlive("Kuai the Brute"),
                    new Action(ctx => BossManager.BossEncounters.FirstOrDefault(b => b.Entry == KuaiTheBrute).MarkAsDead())),

                new Decorator(ctx => boss.Combat,
                    new PrioritySelector(
                        ScriptHelpers.GetBehindUnit(ctx => boss.IsSafelyFacing(Me) && (boss.CastingSpellId == shockWave && StyxWoW.Me.IsTank() || !StyxWoW.Me.IsTank()), () => boss)))
                );
        }

        [EncounterHandler(61444, "Ming the Cunning", Mode = CallBehaviorMode.Proximity)]
        public Composite MingtheCunningEncounter()
        {
            const uint WhilringDervishId = 61626;

            WoWUnit boss = null;
            return new PrioritySelector(
                ctx => boss = ctx as WoWUnit,
                new Decorator(
                    ctx => boss.IsNeutral && ScriptHelpers.IsBossAlive("Ming the Cunning"),
                    new Action(ctx => BossManager.BossEncounters.FirstOrDefault(b => b.Entry == MingTheCunning).MarkAsDead())),

                new Decorator(ctx => boss.Combat,
                    new PrioritySelector(
                        ScriptHelpers.CreateRunAwayFromBad(ctx => true, 20, u => u.Entry == MingTheCunning && ((WoWUnit)u).CastingSpellId == MagneticFieldId),
                        ScriptHelpers.CreateRunAwayFromBad(ctx => true, 15, WhilringDervishId))));
        }

        [EncounterHandler(61445, "Haiyan the Unstoppable", Mode = CallBehaviorMode.Proximity)]
        public Composite HaiyantheUnstoppableEncounter()
        {
            const uint meteorSpellId = 120195;

            WoWUnit boss = null;
            return new PrioritySelector(
                ctx => boss = ctx as WoWUnit,
                new Decorator(
                    ctx => boss.IsNeutral && ScriptHelpers.IsBossAlive("Haiyan the Unstoppable"),
                    new Action(ctx => BossManager.BossEncounters.FirstOrDefault(b => b.Entry == HaiyanTheUnstoppable).MarkAsDead())),

                new Decorator(ctx => boss.Combat,
                    new PrioritySelector(
                // run from party members that have conflagrate debuf.
                /*
                        new Decorator(
                            ctx => meteor != null && Me.Location.DistanceSqr(meteor.ImpactPos) > 10 * 10,
                            new Sequence(
                                new Action(ctx => Logger.Write("Moving to meteor impact location")),
                                new Action(ctx => Navigator.MoveTo(meteor.ImpactPos)))),
                                */
                        ScriptHelpers.CreateRunAwayFromBad(ctx => true, 6, u => u is WoWPlayer && ((WoWPlayer)u).HasAura("Conflagrate") && (u).Guid != Me.Guid),
                        new Decorator(ctx => boss.CastingSpellId == meteorSpellId && boss.CurrentTargetGuid != 0,
                            new Action(ctx =>
                            {
                                var bossTarget = boss.CurrentTarget.ToPlayer();
                                if (bossTarget != null)
                                { // move to the tank if meteor is being casted on me.
                                    if (bossTarget == Me && !Me.IsTank() && bossTarget.Distance > 10)
                                    {
                                        var tank = ScriptHelpers.Tank;
                                        if (tank != null)
                                        {
                                            var moveTo = WoWMathHelper.CalculatePointFrom(Me.Location, tank.Location, 8);
                                            return Navigator.GetRunStatusFromMoveResult(Navigator.MoveTo(moveTo));
                                        }
                                    }
                                    else if (bossTarget != Me && bossTarget.Distance > 10)
                                    {
                                        var moveTo = WoWMathHelper.CalculatePointFrom(Me.Location, bossTarget.Location, 8);
                                        return Navigator.GetRunStatusFromMoveResult(Navigator.MoveTo(moveTo));
                                    }
                                }
                                return RunStatus.Failure;
                            }))))
                );
        }

        [ObjectHandler(214520, "Legacy of the Clan Leaders", ObjectRange = 100)]
        [ObjectHandler(214521, "Legacy of the Clan Leaders Heroic", ObjectRange = 100)]
        public Composite LegacyoftheClanLeadersHandler()
        {
            WoWGameObject chest = null;
            return new PrioritySelector(
                ctx => chest = ctx as WoWGameObject,
                new Decorator(
                    ctx => chest.CanLoot,
                    ScriptHelpers.CreateInteractWithObject(() => chest)));
        }

        [ObjectHandler(214824, "Ancient Mogu Chest")]
        public Composite AncientMoguChestHandler1()
        {
            return CreateMoguChestBehavior();
        }

        [ObjectHandler(214825, "Ancient Mogu Chest")]
        public Composite AncientMoguChestHandler2()
        {
            return CreateMoguChestBehavior();
        }

        [ObjectHandler(214826, "Ancient Mogu Chest")]
        public Composite AncientMoguChestHandler3()
        {
            return CreateMoguChestBehavior();
        }

        [ObjectHandler(214827, "Ancient Mogu Chest")]
        public Composite AncientMoguChestHandler4()
        {
            return CreateMoguChestBehavior();
        }

        Composite CreateMoguChestBehavior()
        {
            int attempts = 0 ;
            DateTime lastInteractTime = DateTime.MinValue;

            WoWGameObject chest = null;
            return new PrioritySelector(
                ctx => chest = ctx as WoWGameObject,

                new Decorator(ctx => attempts >= 3,
                    new Sequence(
                        new Action(ctx => attempts = 0),
                        new DecoratorContinue(ctx => DateTime.Now - lastInteractTime <= TimeSpan.FromSeconds(10),
                            new Action(ctx => Blacklist.Add(chest, TimeSpan.FromMinutes(5))))
                    )),

                new Decorator(
                    ctx => chest.CanLoot && !Blacklist.Contains(chest),
                    new PrioritySelector(
                    new Decorator(ctx => Me.Location.Distance(chest.Location) > 6,
                        new Action(ctx => Navigator.MoveTo(chest.Location))),
                    new Decorator(ctx => Me.Location.Distance(chest.Location) <= 6,
                        new Sequence(
                            new Action(ctx => chest.Interact()),
                            new Action(ctx => attempts++),
                            new Action(ctx => lastInteractTime = DateTime.Now),
                            new WaitContinue(3, ctx => false, new ActionAlwaysSucceed())
                        )))));
        }

        [ObjectHandler(214667, "Ancient Mogu Vault")]
        public Composite AncientMoguVaultHandler()
        {
            const uint ancientMoguKey = 87806;

            WoWGameObject chest = null;
            return new PrioritySelector(
                ctx => chest = ctx as WoWGameObject,
                new Decorator(
                    ctx => chest.CanLoot && Me.BagItems.Any(i => i.Entry == ancientMoguKey),
                    ScriptHelpers.CreateInteractWithObject(() => chest)));
        }

        [EncounterHandler(61243, "Gekkan")]
        public Composite GekkanEncounter()
        {
            WoWUnit boss = null;
            return new PrioritySelector(ctx => boss = ctx as WoWUnit);
        }

        [EncounterHandler(61337, "Glintrok Ironhide")]
        public Composite GlintrokIronhideEncounter()
        {
            const int ironProtector = 118958;
            WoWUnit boss = null;
            return new PrioritySelector(ctx => boss = ctx as WoWUnit,
                ScriptHelpers.CreateInterruptCast(() => boss, ironProtector));
        }


        [EncounterHandler(61340, "Glintrok Hexxer")]
        public Composite GlintrokHexxerEncounter()
        {
            const int hexOfLethargy = 118903;
            WoWUnit boss = null;
            return new PrioritySelector(ctx => boss = ctx as WoWUnit,
                ScriptHelpers.CreateInterruptCast(() => boss, hexOfLethargy),
                ScriptHelpers.CreateDispellParty("Hex of Lethargy", ScriptHelpers.PartyDispellType.Magic));
        }

        [EncounterHandler(61339, "Glintrok Oracle")]
        public Composite GlintrokOracleEncounter()
        {
            const int cleansingFlame = 118940;
            WoWUnit boss = null;
            return new PrioritySelector(ctx => boss = ctx as WoWUnit,
                ScriptHelpers.CreateInterruptCast(() => boss, cleansingFlame));
        }

        [ObjectHandler(214795, "Ancient Mogu Treasure")]
        [ObjectHandler(214794, "Ancient Mogu Treasure Heroic")]
        public Composite AncientMoguTreasureHandler() { return CreateMoguChestBehavior(); }

        [EncounterHandler(61398, "Xin the Weaponmaster")]
        public Composite XintheWeaponmasterEncounter()
        {
            var spearStreamStart1 = new WoWPoint(-4653.144, -2571.559, 27.28992);
            var spearStreamEnd1 = new WoWPoint(-4635.11, -2642.283, 27.75975);

            var spearStreamStart2 = new WoWPoint(-4612.325, -2655.249, 27.23762);
            var spearStreamEnd2 = new WoWPoint(-4594.599, -2584.786, 27.18633);

            var spearStreamStart3 = new WoWPoint(-4613.093, -2655.249, 27.23765);
            var spearStreamEnd3 = new WoWPoint(-4630.541, -2584.572, 27.10564);

            var spearStreamStart4 = new WoWPoint(-4654.006, -2571.327, 27.28995);
            var spearStreamEnd4 = new WoWPoint(-4671.301, -2642.371, 27.81245);

            const uint animatedAxe = 61451;
            const uint ringOfFire = 61499;
            const int groundSlam = 119684;
            var tankLoc = new WoWPoint(-4654.811, -2613.644, 21.90685);
            WoWUnit boss = null;
            return new PrioritySelector(
                ctx => boss = ctx as WoWUnit,
                //ScriptHelpers.CreateAvoidUnitAnglesBehavior(  // Todo uncomment this once HB core has this func 
                //    ctx =>  boss.CastingSpellId == groundSlam && StyxWoW.Me.IsTank() || !StyxWoW.Me.IsTank(), () => boss, new ScriptHelpers.AngleSpan(0, 35)),
                ScriptHelpers.GetBehindUnit(ctx => boss.IsSafelyFacing(Me, 50) && (boss.CastingSpellId == groundSlam && StyxWoW.Me.IsTank() || !StyxWoW.Me.IsTank()), () => boss),
                ScriptHelpers.CreateRunAwayFromBad(ctx => true, 8, ringOfFire),
                ScriptHelpers.CreateRunAwayFromBad(ctx => true, 12, u => u.Entry == animatedAxe && ((WoWUnit)u).HasAura("Whirlwind") && u.DistanceSqr <= 20 * 20),
                ScriptHelpers.CreateRunAwayFromLocation(
                    ctx => boss.HealthPercent <= 60 && !Me.IsMoving && boss.CastingSpellId != groundSlam, 4, arg => Me.Location.GetNearestPointOnSegment(spearStreamStart1, spearStreamEnd1)),
                ScriptHelpers.CreateRunAwayFromLocation(ctx => boss.HealthPercent <= 60 && !Me.IsMoving && boss.CastingSpellId != groundSlam, 4, arg => Me.Location.GetNearestPointOnSegment(spearStreamStart2, spearStreamEnd2)),
                ScriptHelpers.CreateRunAwayFromLocation(ctx => boss.HealthPercent <= 60 && !Me.IsMoving && boss.CastingSpellId != groundSlam, 4, arg => Me.Location.GetNearestPointOnSegment(spearStreamStart3, spearStreamEnd3)),
                ScriptHelpers.CreateRunAwayFromLocation(ctx => boss.HealthPercent <= 60 && !Me.IsMoving && boss.CastingSpellId != groundSlam, 4, arg => Me.Location.GetNearestPointOnSegment(spearStreamStart4, spearStreamEnd4)),
                ScriptHelpers.CreateTankUnitAtLocation(() => tankLoc, 30));
        }
    }
}